import _ from 'lodash';
import { AgentSimulationJobCount, SimulationJobDto, SimulationJobPageQuery } from '../dtos';
import { getCustomRepository } from 'typeorm';
import { SimulationJobRepository } from '../repositories';
import { SimJobStatus, SimJobType } from '../type';
import { entityToDto, dtoToEntityLike } from '../assembler';
import { SimulationJob } from '../entities/simulationJob';


export interface ISimulationJobService {
  save(dto: SimulationJobDto): Promise<number>;
  findAll(): Promise<SimulationJobDto[]>;
  findById(id: number | string): Promise<SimulationJobDto | undefined>;
  findByStatus(status: SimJobStatus): Promise<SimulationJobDto[]>;
  findByContext(userId: string, appId: string, nodeId: string): Promise<SimulationJobDto[]>;
  findByProductAndStatus(productName: string, status: SimJobStatus): Promise<SimulationJobDto[]>;
  findByContextAndBusinessId(userId: string, appId: string, nodeId: string, businessId: string): Promise<SimulationJobDto | undefined>;
  deleteByContextAndBusinessIds(userId: string, appId: string, nodeId: string, businessIds: string[]): Promise<number[]>;
  findForPage(simulationJobPageQuery: SimulationJobPageQuery): Promise<any>;
  countAgentSimulationJobStatus(agents: string[], statuses: string[], product: string): Promise<AgentSimulationJobCount[]>;
  updateProgressByJobId(jobId: number, progress: number): Promise<void>;
  countJobByAgentAndStatuses(agents: string[], statuses: string[], product: string): Promise<AgentSimulationJobCount[]>;
  deleteByIds(jobIds: string[]): Promise<number[]>;
  findTimeoutJobs(timeout: number): Promise<SimulationJobDto[]>;
  findByNodeIdAndStatuses(nodeId: string, statuses: string[]): Promise<SimulationJobDto[]>;
  updateSubJobByJobId(jobId: number, parentJobId: number): Promise<void>;
  getLatestByBusinessIdAndProduct(businessId: string, product: string): Promise<SimulationJobDto>;
}

export class SimulationJobService implements ISimulationJobService {

  private simJobRepository: SimulationJobRepository;

  constructor() {
    this.simJobRepository = getCustomRepository(SimulationJobRepository);
  }

  async getLatestByBusinessIdAndProduct(businessId: string, product: string): Promise<SimulationJobDto> {
    let simulationJob = await this.simJobRepository.getLatestByBusinessIdAndProduct(businessId, product);
    if (simulationJob) {
      return entityToDto(simulationJob);
    }
  }

  async findByContext(userId: string, appId: string, nodeId: string): Promise<SimulationJobDto[]> {
    const simulationJobs = await this.simJobRepository.findByContext(userId, appId, nodeId);
    return simulationJobs.map(job => entityToDto(job));
  }

  async findAll(): Promise<SimulationJobDto[]> {
    const simulationJobs = await this.simJobRepository.find();
    return simulationJobs.map(job => entityToDto(job));
  }

  async findById(id: number | string): Promise<SimulationJobDto | undefined> {
    id = _.isString(id) ? id : _.toNumber(id);
    const simulationJob = await this.simJobRepository.findOne(id);
    if (simulationJob) {
      return entityToDto(simulationJob);
    }
  }

  async findByStatus(status: SimJobStatus): Promise<SimulationJobDto[]> {
    const simulationJobs = await this.simJobRepository.findByStatus(status);
    return simulationJobs.map(job => entityToDto(job));
  }

  async findByProductAndStatus(productName: string, status: SimJobStatus): Promise<SimulationJobDto[]> {
    const simulationJobs = await this.simJobRepository.findByProductAndStatus(productName, status);
    return simulationJobs.map(job => entityToDto(job));
  }

  async save(dto: SimulationJobDto): Promise<number> {
    const jobEntity = this.simJobRepository.create(dtoToEntityLike(dto));
    await this.simJobRepository.save(jobEntity);
    return jobEntity.id;
  }

  async findByContextAndBusinessId(userId: string, appId: string, nodeId: string, businessId: string) {
    const job = await this.simJobRepository.findByContextAndBusinessId(userId, appId, nodeId, businessId);
    return entityToDto(job);
  }

  async deleteByContextAndBusinessIds(userId: string, appId: string, nodeId: string, businessIds: string[]): Promise<number[]> {
    const jobs = await this.simJobRepository.findByContextAndBusinessIds(userId, appId, nodeId, businessIds);
    if (_.isEmpty(jobs)) {
      return [];
    }
    await this.simJobRepository.remove(jobs);
    return jobs.map(job => job.id);
  }

  async findForPage(simulationJobPageQuery: SimulationJobPageQuery): Promise<any> {
    const pageData = await this.simJobRepository.findForPage(simulationJobPageQuery);
    pageData.data = pageData.data.map(job => entityToDto(job));
    return pageData;
  }

  async countAgentSimulationJobStatus(agents: string[], statuses: string[], product: string): Promise<AgentSimulationJobCount[]> {
    return await this.simJobRepository.countAgentSimulationJobStatus(agents, statuses, product);
  }

  async updateProgressByJobId(jobId: number, progress: number): Promise<void> {
    const job = await this.simJobRepository.findOne(jobId);
    if (job) {
      job.progress = progress;
      await this.simJobRepository.save(job);
    }
  }

  async countJobByAgentAndStatuses(agents: string[], statuses: string[], product: string): Promise<AgentSimulationJobCount[]> {
    return await this.simJobRepository.countJobByAgentAndStatuses(agents, statuses, product);
  }

  async deleteByIds(jobIds: string[]): Promise<number[]> {
    const jobs = await this.simJobRepository.findByIds(jobIds);
    if (_.isEmpty(jobs)) {
      return [];
    }
    await this.simJobRepository.remove(jobs);
    return jobs.map(job => job.id);
  }

  public async findTimeoutJobs(timeout: number): Promise<SimulationJobDto[]> {
    const jobs = await this.simJobRepository.findTimeoutJobs(timeout);
    return jobs.map(jobEntity => entityToDto(jobEntity));
  }

  async findByNodeIdAndStatuses(nodeId: string, statuses: string[]): Promise<SimulationJobDto[]> {
    const simulationJobs = await this.simJobRepository.findByNodeIdAndStatuses(nodeId, statuses);
    return simulationJobs.map(job => entityToDto(job));
  }

  async updateSubJobByJobId(jobId: number, parentJobId: number): Promise<void> {
    const job = await this.simJobRepository.findOne(jobId);
    console.log(`jobId = ${jobId} updateSubJobByJobId, job = ${JSON.stringify(job)}`);
    if (job) {
      job.parentJobId = parentJobId;
      job.type = SimJobType.Sub;
      await this.simJobRepository.save(job);
    }
  }


}
